//////////////////////////////////////////////////////////////////////////
// PeekPocket - the hassle-free WiFi scanner for Windows Mobile
// coded by dzolee
// http://dzolee.blogspot.com
//
// Data storage - provides access to AP scan data. Contains items indexed
// by both MAC address and ID.
//////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "DataStorage.h"
#include "SettingsStorage.h"
#include "TextCache.h"

extern CSettingsStorage g_Settings;
extern CTextCache g_Texts;

#define LOCK EnterCriticalSection(&m_Lock);
#define UNLOCK LeaveCriticalSection(&m_Lock);

//////////////////////////////////////////////////////////////////////////
CDataStorage::CDataStorage()
{
	InitializeCriticalSection(&m_Lock);
}

//////////////////////////////////////////////////////////////////////////
CDataStorage::~CDataStorage()
{
	DeleteCriticalSection(&m_Lock);
	SerializeTxt();
	Clear();
}

//////////////////////////////////////////////////////////////////////////
//remove all stored items
void CDataStorage::Clear()
{
POSITION pos;
DWORD i;
DisplayInfo *pStored;

	//free items
	pos=m_IDMap.GetStartPosition();
	while(pos != NULL)
	{
		m_IDMap.GetNextAssoc(pos, i, pStored);
		if(pStored != NULL)
		{
			delete pStored;
		}
	}

	//empty maps
	m_IDMap.RemoveAll();
	m_MACMap.RemoveAll();

	m_TmpArray.RemoveAll();
}

//////////////////////////////////////////////////////////////////////////
//process scan results
void CDataStorage::ProcessInput(struct BSSIDInfo *pInfo, DWORD dwNum)
{
DWORD i, dwSoundOff, dwHideSec, dwHidePeer;
DisplayInfo *pStored;
POSITION pos;
SYSTEMTIME time;
DisplayInfoArray arActive, arInactive;

	g_Settings.GetNumeric(nseHideWEP, dwHideSec);
	g_Settings.GetNumeric(nseHidePeer, dwHidePeer);
	g_Settings.GetNumeric(nseSoundOff, dwSoundOff);

	LOCK;

	//reset active flag for items
	pos=m_IDMap.GetStartPosition();
	while(pos != NULL)
	{
		m_IDMap.GetNextAssoc(pos, i, pStored);
		pStored->bActive=false;
	}

	//check received entries
	for(i=0; i<dwNum; i++)
	{
		//already known?
		if(m_MACMap.Lookup(pInfo[i].BSSID, pStored) == TRUE)
		{
			pStored->bActive=true;	
			
			//update data
			pStored->bActive=true;
			pStored->Channel=pInfo[i].Channel;
			pStored->Infastructure=pInfo[i].Infastructure;
			pStored->Auth=pInfo[i].Auth;
			pStored->RSSI=pInfo[i].RSSI;
			wcscpy(pStored->SSID, pInfo[i].SSID);

			//current is the strongest so far?
			if(pStored->iBestSig < pStored->RSSI)
			{
				pStored->iBestSig=pStored->RSSI;
			}

			if(pStored->iWorstSig > pStored->RSSI)
			{
				pStored->iWorstSig=pStored->RSSI;
			}

			//update last seen
			GetLocalTime(&time);
			COleDateTime dt(time);

			pStored->dLastSeen=dt;
		}
		else
		//no, a new one
		{
			//alloc new struct
			pStored=new DisplayInfo();
			if(pStored != NULL)
			{
				//fill fields
				pStored->bActive=true;
				pStored->Channel=pInfo[i].Channel;
				pStored->Infastructure=pInfo[i].Infastructure;
				pStored->Auth=pInfo[i].Auth;
				pStored->RSSI=pInfo[i].RSSI;
				pStored->iBestSig=pStored->iWorstSig=pStored->RSSI;

				//create first seen, last seen
				GetLocalTime(&time);
				COleDateTime dt(time);

				pStored->dFirstSeen=dt;
				pStored->dLastSeen=dt;

				memcpy(pStored->BSSID, pInfo[i].BSSID, 6);
				wcscpy(pStored->SSID, pInfo[i].SSID);

				pStored->ID=m_IDMap.GetCount();
				
				m_MACMap.SetAt(pStored->BSSID, pStored);
				m_IDMap.SetAt(pStored->ID, pStored);

				//a new one, play sound, if passes filters
				if( (dwHideSec == 0 || pStored->Auth == Ndis802_11AuthModeOpen) && (dwHidePeer == 0 || pStored->Infastructure == Ndis802_11Infrastructure) )
				{
					if(dwSoundOff == 0)		//erm, need to flip this value somewhere
					{
						//play different sound for open/secure APs
						if(pStored->Auth == Ndis802_11AuthModeOpen)
						{
							PlaySound(MAKEINTRESOURCE(IDR_AP_OPEN_WAV), AfxGetResourceHandle(), SND_RESOURCE | SND_ASYNC);
						}
						else
						{
							PlaySound(MAKEINTRESOURCE(IDR_AP_WEP_WAV), AfxGetResourceHandle(), SND_RESOURCE | SND_ASYNC);
						}
					}
				}
			}//alloc ok
		}//a new AP
	}//for
	UNLOCK;
}

//////////////////////////////////////////////////////////////////////////
//get all data of a certain AP
bool CDataStorage::GetFilteredItemByIndex(DWORD dwIdx, DisplayInfo *pOut)
{
bool bRetval;

	LOCK;
	if(dwIdx < (DWORD)m_TmpArray.GetCount())
	{
		*pOut=*m_TmpArray[dwIdx];
		bRetval=true;
	}
	else
	{
		bRetval=false;
	}
	UNLOCK;

	return bRetval;
}

//////////////////////////////////////////////////////////////////////////
bool CDataStorage::GetItemByID(DWORD dwID, DisplayInfo *pOut)
{
DisplayInfo *p;
bool bRetval;

	bRetval=false;

	LOCK;
	if(TRUE == m_IDMap.Lookup(dwID, p))
	{
		*pOut=*p;
		bRetval=true;
	}
	else
	{
		bRetval=false;
	}
	UNLOCK;

	return bRetval;
}

//////////////////////////////////////////////////////////////////////////
//get item count with respect to current filters (secure APs, peers)
void CDataStorage::GetCounts(DWORD &dwFiltered, DWORD &dwFilteredActive)
{
POSITION pos;
DisplayInfo *pStored;
DWORD i, act, filt, dwHideSec, dwHidePeer;
DisplayInfoArray arActive, arInactive;

	g_Settings.GetNumeric(nseHideWEP, dwHideSec);
	g_Settings.GetNumeric(nseHidePeer, dwHidePeer);

	act=filt=dwFiltered=dwFilteredActive=0;

	LOCK;
	//iterate thru items
	pos=m_IDMap.GetStartPosition();
	while(pos != NULL)
	{
		m_IDMap.GetNextAssoc(pos, i, pStored);
		//check only those that pass filter
		if( (dwHideSec != 0 && pStored->Auth != Ndis802_11AuthModeOpen) || (dwHidePeer != 0 && pStored->Infastructure != Ndis802_11Infrastructure) )
		{
			continue;
		}
		else
		{
			//increment counts and fill arrays
			filt++;
			if(pStored->bActive == true)
			{
				act++;
				arActive.Add(pStored);
			}
			else
			{
				//inactive
				arInactive.Add(pStored);
			}
		}
	}//while
	UNLOCK;

	//after the while loop, we have 2 arrays, one filled with active
	//and another filled with inactive items.
	//now concatenate two to form final array
	m_TmpArray.RemoveAll();
	m_TmpArray.Append(arActive);
	m_TmpArray.Append(arInactive);

	//return results
	dwFiltered=filt;
	dwFilteredActive=act;

	ASSERT(m_TmpArray.GetCount() == filt && arActive.GetCount() == act);
}

//////////////////////////////////////////////////////////////////////////
//save AP data to disk
void CDataStorage::SerializeTxt()
{
int curPos;
CString strTemp, strData, strPath;
TCHAR chBuff[MAX_PATH];
POSITION pos;
DWORD i;
DisplayInfo *pStored;
CFile file;

	if(m_IDMap.IsEmpty())
	{
		//nothing to do
		return;
	}

	//get current path
	memset(chBuff, 0, sizeof(chBuff));
	strPath=strTemp=_T("");
	GetModuleFileName(NULL, chBuff, MAX_PATH-1);
	strTemp=chBuff;
	curPos=strTemp.ReverseFind(_T('\\'));
	if(curPos == -1)
	{
		//its in ROOT
		strPath=_T("\\");
	}
	else
	{
		strPath=strTemp.Left(curPos);
	}

	strPath+=_T("\\PeekPocket.txt");

	if(file.Open(strPath, CFile::modeCreate | CFile::modeReadWrite))
	{
		//unicode, little endian: store marker bytes
#ifdef _UNICODE
		BYTE buf[2];
		buf[0]=0xff;
		buf[1]=0xfe;
		file.Write((void *)&buf[0], 2);
#else
#error Only unicode serialization is supported.
#endif

		//store header
		strData = g_Texts.GetString(teName) + _T("\t") + g_Texts.GetString(teSig) + _T("\t");
		strData+= g_Texts.GetString(teCh) + _T("\t") + g_Texts.GetString(teType) + _T("\t");
		strData+= g_Texts.GetString(teSec) + _T("\t");
		strData+= g_Texts.GetString(teBestSig) + _T("\t") + g_Texts.GetString(teWorstSig) + _T("\t");
		strData+= g_Texts.GetString(teFirstSeen) + _T("\t") + g_Texts.GetString(teLastSeen) + _T("\t");
		strData+= g_Texts.GetString(teMAC);
		
		strData+=_T("\r\n");

		file.Write(strData.GetBuffer(), sizeof(TCHAR) * strData.GetLength());
		//iterate thru items
		pos=m_IDMap.GetStartPosition();
		while(pos != NULL)
		{
			m_IDMap.GetNextAssoc(pos, i, pStored);
			//serialize one element

			//fill string with data
			strData=pStored->SSID;
			strData+= _T("\t");		//add name
			
			if(pStored->bActive == true)			//add signal
			{
				strTemp.Format(_T("%d\t"), pStored->RSSI);
			}
			else
			{
				strTemp=_T("--\t");
			}
			strData+=strTemp;
			strTemp.Format(_T("%d\t"), pStored->Channel);		//add channel
			strData+=strTemp;
			
			if(pStored->Infastructure == Ndis802_11IBSS)		//add type
			{
				strTemp=g_Texts.GetString(tePeer);
			}
			else
			{
				strTemp=g_Texts.GetString(teAP);
			}
			strData+=strTemp+_T("\t");
			
/*
			if(pStored->Auth == Ndis802_11AuthModeOpen)			//add security
			{
				strTemp=g_Texts.GetString(teOpen);
			}
			else
			{
				strTemp=g_Texts.GetString(teWEP);
			}
*/
			switch(pStored->Auth)
			{
				case Ndis802_11AuthModeOpen:
				{
					strTemp=g_Texts.GetString(teOpen);
					break;
				}
				case Ndis802_11AuthModeWPA_XP:
				{
					//this is the same in all languages so far..
					strTemp=_T("WPA");
					break;
				}
				case Ndis802_11AuthModeWPA2_XP:
				{
					//this is the same in all languages so far..
					strTemp=_T("WPA2");
					break;
				}
				default:
				{
					//this is the same in all languages so far..
					strTemp=_T("WEP");
				}
			}//switch auth

			strData+=strTemp+_T("\t");

																//add best/worst signals
			strTemp.Format(_T("%d\t%d\t"), pStored->iBestSig, pStored->iWorstSig);
			strData+=strTemp;

			try
			{
				COleDateTime dFirst(pStored->dFirstSeen);

				//could use COleDateTime::Format
				strTemp.Format(_T("%02d/%02d/%02d %02d:%02d:%02d\t"), dFirst.GetYear() % 1000, dFirst.GetMonth(), dFirst.GetDay(), dFirst.GetHour(), dFirst.GetMinute(), dFirst.GetSecond());

				strData+=strTemp;
			}
			catch (...)
			{
			}

			try
			{
				COleDateTime dFirst(pStored->dLastSeen);

				//could use COleDateTime::Format
				strTemp.Format(_T("%02d/%02d/%02d %02d:%02d:%02d\t"), dFirst.GetYear() % 1000, dFirst.GetMonth(), dFirst.GetDay(), dFirst.GetHour(), dFirst.GetMinute(), dFirst.GetSecond());

				strData+=strTemp;
			}
			catch (...)
			{
			}

			//add MAC
			strTemp.Format(_T("%02x-%02x-%02x-%02x-%02x-%02x"), pStored->BSSID[0], pStored->BSSID[1], pStored->BSSID[2], pStored->BSSID[3], pStored->BSSID[4], pStored->BSSID[5]);
			strData+=strTemp;

			//save row
			strData+=_T("\r\n");
			file.Write(strData.GetBuffer(), sizeof(TCHAR) * strData.GetLength());
		}
		//close file
		file.Close();
	}//if file creation ok
#ifdef _DEBUG
	else
	{
		OutputDebugString(_T("Failed to create output file.\n"));
	}
#endif

}

//////////////////////////////////////////////////////////////////////////
//this fxn gets called regularly to get rid of duplicate AP names.
//the list is scanned for duplicate names, and dupes not seen in the last minutes, are removed.
void CDataStorage::DeleteOldDuplicates()
{
DWORD i, a, acount, icount;
DisplayInfo *pStored;
POSITION pos;
DisplayInfoArray arActive, arInactive;
SYSTEMTIME st;

	LOCK;
	pos=m_IDMap.GetStartPosition();
	
	//divide into active and inactive element arrays
	while(pos != NULL)
	{
		m_IDMap.GetNextAssoc(pos, i, pStored);
		if(pStored->bActive == true)
		{
			arActive.Add(pStored);
		}
		else
		{
			arInactive.Add(pStored);
		}
	}//while

	//for every inactive item, check if there is an active item with the same name and type
	icount=arInactive.GetCount();
	acount=arActive.GetCount();

	GetLocalTime(&st);
	COleDateTime dtNow(st);

	for(i=0; i<icount; i++)
	{
		//for every inactive item, find a corresponding active item
		
		// - items will be marked for deletion by using their bActive field -
		arInactive[i]->bActive=true;
		
		for(a=0; a<acount; a++)
		{
			//name matches?
			if(wcscmp(arInactive[i]->SSID, arActive[a]->SSID) == 0)
			{
				//type and auth matches?
				if(arInactive[i]->Auth == arActive[a]->Auth && arInactive[i]->Infastructure == arActive[a]->Infastructure)
				{
					//very close, now check last seen time
					COleDateTime dtItem(arInactive[i]->dLastSeen);

					COleDateTimeSpan dtSpan=dtNow-dtItem;

					int iSeconds;

					iSeconds=(int)dtSpan.GetTotalSeconds();
					if(iSeconds > 120)
					{
						//delete this item!
						arInactive[i]->bActive=false;
					}
					break;	// -> continue with next inactive item, exit inner (active) loop
				}//type and auth matches
			}//ap name matches
		}//for active items
	}//for inactive items

	//now every element has been checked and marked if necessary.
	//delete them!
	icount=arInactive.GetCount();
	for(i=0; i<icount; i++)
	{
		if(arInactive[i]->bActive == false)
		{
			//remove from ID map
			m_IDMap.RemoveKey(arInactive[i]->ID);
			//remove from MAC map
			m_MACMap.RemoveKey(arInactive[i]->BSSID);

			//dealloc memory
			delete arInactive[i];
		}
		else
		{
			//reset active flag to original - which was "inactive" because item is in array of inactive items
			arInactive[i]->bActive=false;
		}
	}
	UNLOCK;

	arActive.RemoveAll();
	arInactive.RemoveAll();
}